{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "aad4e28d",
      "metadata": {},
      "source": [
        "# Persistence\n",
        "\n",
        "Many AI applications need memory to share context across multiple interactions in a single conversational \"thread.\"\n",
        "In LangGraph, this type of conversation-level memory can be added to any graph using\n",
        "[Checkpointers](https://langchain-ai.github.io/langgraphjs/reference/interfaces/index.Checkpoint.html).\n",
        "\n",
        "Just compile the graph with a compatible checkpointer. Below is an example using a simple in-memory \"MemorySaver\":\n",
        "\n",
        "```javascript\n",
        "import { MemorySaver } from \"@langchain/langgraph\";\n",
        "\n",
        "const checkpointer = new MemorySaver();\n",
        "const graph = workflow.compile({ checkpointer });\n",
        "```\n",
        "\n",
        "This guide shows how you can add thread-level persistence to your graph.\n",
        "\n",
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note: multi-conversation memory</p>\n",
        "    <p>\n",
        "        If you need memory that is <b>shared</b> across multiple conversations or users (cross-thread persistence), check out this <a href=\"https://langchain-ai.github.io/langgraphjs/how-tos/cross-thread-persistence/\">how-to guide</a>).\n",
        "    </p>\n",
        "</div>\n",
        "\n",
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "        In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>createReactAgent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraphjs/reference/functions/prebuilt.createReactAgent.html\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain's <a href=\"https://js.langchain.com/docs/how_to/agent_executor\">AgentExecutor</a> class.\n",
        "    </p>\n",
        "</div>\n",
        "\n",
        "## Setup\n",
        "\n",
        "This guide will use OpenAI's GPT-4o model. We will optionally set our API key\n",
        "for [LangSmith tracing](https://smith.langchain.com/), which will give us\n",
        "best-in-class observability."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "10021b8c",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Persistence: LangGraphJS\n"
          ]
        }
      ],
      "source": [
        "// process.env.OPENAI_API_KEY = \"sk_...\";\n",
        "\n",
        "// Optional, add tracing in LangSmith\n",
        "// process.env.LANGCHAIN_API_KEY = \"ls__...\";\n",
        "process.env.LANGCHAIN_CALLBACKS_BACKGROUND = \"true\";\n",
        "process.env.LANGCHAIN_TRACING_V2 = \"true\";\n",
        "process.env.LANGCHAIN_PROJECT = \"Persistence: LangGraphJS\";"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b9e252c",
      "metadata": {},
      "source": [
        "## Define the state\n",
        "\n",
        "The state is the interface for all of the nodes in our graph.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "9fc47087",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "import { Annotation } from \"@langchain/langgraph\";\n",
        "import { BaseMessage } from \"@langchain/core/messages\";\n",
        "\n",
        "const GraphState = Annotation.Root({\n",
        "  messages: Annotation<BaseMessage[]>({\n",
        "    reducer: (x, y) => x.concat(y),\n",
        "  }),\n",
        "});"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bdba79f",
      "metadata": {},
      "source": [
        "## Set up the tools\n",
        "\n",
        "We will first define the tools we want to use. For this simple example, we will\n",
        "use create a placeholder search engine. However, it is really easy to create\n",
        "your own tools - see documentation\n",
        "[here](https://js.langchain.com/docs/how_to/custom_tools) on how to do\n",
        "that."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5f1e5deb",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "import { tool } from \"@langchain/core/tools\";\n",
        "import { z } from \"zod\";\n",
        "\n",
        "const searchTool = tool(async ({}: { query: string }) => {\n",
        "  // This is a placeholder for the actual implementation\n",
        "  return \"Cold, with a low of 13 ℃\";\n",
        "}, {\n",
        "  name: \"search\",\n",
        "  description:\n",
        "    \"Use to surf the web, fetch current information, check the weather, and retrieve other information.\",\n",
        "  schema: z.object({\n",
        "    query: z.string().describe(\"The query to use in your search.\"),\n",
        "  }),\n",
        "});\n",
        "\n",
        "await searchTool.invoke({ query: \"What's the weather like?\" });\n",
        "\n",
        "const tools = [searchTool];"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a5615fd8",
      "metadata": {},
      "source": [
        "We can now wrap these tools in a simple\n",
        "[ToolNode](/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html).\n",
        "This object will actually run the tools (functions) whenever they are invoked by\n",
        "our LLM."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "1852d2a4",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n",
        "\n",
        "const toolNode = new ToolNode(tools);"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a593cc20",
      "metadata": {},
      "source": [
        "## Set up the model\n",
        "\n",
        "Now we will load the\n",
        "[chat model](https://js.langchain.com/docs/concepts/#chat-models).\n",
        "\n",
        "1. It should work with messages. We will represent all agent state in the form\n",
        "   of messages, so it needs to be able to work well with them.\n",
        "2. It should work with\n",
        "   [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms),\n",
        "   meaning it can return function arguments in its response.\n",
        "\n",
        "<div class=\"admonition tip\">\n",
        "    <p class=\"admonition-title\">Note</p>\n",
        "    <p>\n",
        "        These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.\n",
        "    </p>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "77c9701b",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "import { ChatOpenAI } from \"@langchain/openai\";\n",
        "\n",
        "const model = new ChatOpenAI({ model: \"gpt-4o\" });"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4177b143",
      "metadata": {},
      "source": [
        "After we've done this, we should make sure the model knows that it has these\n",
        "tools available to call. We can do this by calling\n",
        "[bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "b35d9bd2",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "const boundModel = model.bindTools(tools);"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bbb0ae12",
      "metadata": {},
      "source": [
        "## Define the graph\n",
        "\n",
        "We can now put it all together. We will run it first without a checkpointer:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "5f85457b",
      "metadata": {},
      "outputs": [],
      "source": [
        "import { END, START, StateGraph } from \"@langchain/langgraph\";\n",
        "import { AIMessage } from \"@langchain/core/messages\";\n",
        "import { RunnableConfig } from \"@langchain/core/runnables\";\n",
        "\n",
        "const routeMessage = (state: typeof GraphState.State) => {\n",
        "  const { messages } = state;\n",
        "  const lastMessage = messages[messages.length - 1] as AIMessage;\n",
        "  // If no tools are called, we can finish (respond to the user)\n",
        "  if (!lastMessage.tool_calls?.length) {\n",
        "    return END;\n",
        "  }\n",
        "  // Otherwise if there is, we continue and call the tools\n",
        "  return \"tools\";\n",
        "};\n",
        "\n",
        "const callModel = async (\n",
        "  state: typeof GraphState.State,\n",
        "  config?: RunnableConfig,\n",
        ") => {\n",
        "  const { messages } = state;\n",
        "  const response = await boundModel.invoke(messages, config);\n",
        "  return { messages: [response] };\n",
        "};\n",
        "\n",
        "const workflow = new StateGraph(GraphState)\n",
        "  .addNode(\"agent\", callModel)\n",
        "  .addNode(\"tools\", toolNode)\n",
        "  .addEdge(START, \"agent\")\n",
        "  .addConditionalEdges(\"agent\", routeMessage)\n",
        "  .addEdge(\"tools\", \"agent\");\n",
        "\n",
        "const graph = workflow.compile();"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "41364864",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hi I'm Yu, nice to meet you.\n",
            "-----\n",
            "\n",
            "Hi Yu! Nice to meet you too. How can I assist you today?\n",
            "-----\n",
            "\n"
          ]
        }
      ],
      "source": [
        "let inputs = { messages: [{ role: \"user\", content: \"Hi I'm Yu, nice to meet you.\" }] };\n",
        "for await (\n",
        "  const { messages } of await graph.stream(inputs, {\n",
        "    streamMode: \"values\",\n",
        "  })\n",
        ") {\n",
        "  let msg = messages[messages?.length - 1];\n",
        "  if (msg?.content) {\n",
        "    console.log(msg.content);\n",
        "  } else if (msg?.tool_calls?.length > 0) {\n",
        "    console.log(msg.tool_calls);\n",
        "  } else {\n",
        "    console.log(msg);\n",
        "  }\n",
        "  console.log(\"-----\\n\");\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "ccddfd4a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Remember my name?\n",
            "-----\n",
            "\n",
            "You haven't shared your name with me yet. What's your name?\n",
            "-----\n",
            "\n"
          ]
        }
      ],
      "source": [
        "inputs = { messages: [{ role: \"user\", content: \"Remember my name?\" }] };\n",
        "for await (\n",
        "  const { messages } of await graph.stream(inputs, {\n",
        "    streamMode: \"values\",\n",
        "  })\n",
        ") {\n",
        "  let msg = messages[messages?.length - 1];\n",
        "  if (msg?.content) {\n",
        "    console.log(msg.content);\n",
        "  } else if (msg?.tool_calls?.length > 0) {\n",
        "    console.log(msg.tool_calls);\n",
        "  } else {\n",
        "    console.log(msg);\n",
        "  }\n",
        "  console.log(\"-----\\n\");\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3bece060",
      "metadata": {},
      "source": [
        "## Add Memory\n",
        "\n",
        "Let's try it again with a checkpointer. We will use the\n",
        "[MemorySaver](/langgraphjs/reference/classes/index.MemorySaver.html),\n",
        "which will \"save\" checkpoints in-memory."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "217ac741",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [],
      "source": [
        "import { MemorySaver } from \"@langchain/langgraph\";\n",
        "\n",
        "// Here we only save in-memory\n",
        "const memory = new MemorySaver();\n",
        "const persistentGraph = workflow.compile({ checkpointer: memory });"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "173c17f9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hi I'm Jo, nice to meet you.\n",
            "-----\n",
            "\n",
            "Hello Jo, nice to meet you too! How can I assist you today?\n",
            "-----\n",
            "\n"
          ]
        }
      ],
      "source": [
        "let config = { configurable: { thread_id: \"conversation-num-1\" } };\n",
        "inputs = { messages: [{ role: \"user\", content: \"Hi I'm Jo, nice to meet you.\" }] };\n",
        "for await (\n",
        "  const { messages } of await persistentGraph.stream(inputs, {\n",
        "    ...config,\n",
        "    streamMode: \"values\",\n",
        "  })\n",
        ") {\n",
        "  let msg = messages[messages?.length - 1];\n",
        "  if (msg?.content) {\n",
        "    console.log(msg.content);\n",
        "  } else if (msg?.tool_calls?.length > 0) {\n",
        "    console.log(msg.tool_calls);\n",
        "  } else {\n",
        "    console.log(msg);\n",
        "  }\n",
        "  console.log(\"-----\\n\");\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "1162eb84",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Remember my name?\n",
            "-----\n",
            "\n",
            "Yes, I'll remember that your name is Jo. How can I assist you today?\n",
            "-----\n",
            "\n"
          ]
        }
      ],
      "source": [
        "inputs = { messages: [{ role: \"user\", content: \"Remember my name?\"}] };\n",
        "for await (\n",
        "  const { messages } of await persistentGraph.stream(inputs, {\n",
        "    ...config,\n",
        "    streamMode: \"values\",\n",
        "  })\n",
        ") {\n",
        "  let msg = messages[messages?.length - 1];\n",
        "  if (msg?.content) {\n",
        "    console.log(msg.content);\n",
        "  } else if (msg?.tool_calls?.length > 0) {\n",
        "    console.log(msg.tool_calls);\n",
        "  } else {\n",
        "    console.log(msg);\n",
        "  }\n",
        "  console.log(\"-----\\n\");\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "73902faf",
      "metadata": {},
      "source": [
        "## New Conversational Thread\n",
        "\n",
        "If we want to start a new conversation, we can pass in a different\n",
        "**`thread_id`**. Poof! All the memories are gone (just kidding, they'll always\n",
        "live in that other thread)!\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "58cc0612",
      "metadata": {
        "lines_to_next_cell": 2
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{ configurable: { thread_id: 'conversation-2' } }\n"
          ]
        }
      ],
      "source": [
        "config = { configurable: { thread_id: \"conversation-2\" } };"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "25aea87b",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "you forgot?\n",
            "-----\n",
            "\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Could you please provide more context or details about what you are referring to? This will help me assist you better.\n",
            "-----\n",
            "\n"
          ]
        }
      ],
      "source": [
        "inputs = { messages: [{ role: \"user\", content: \"you forgot?\" }] };\n",
        "for await (\n",
        "  const { messages } of await persistentGraph.stream(inputs, {\n",
        "    ...config,\n",
        "    streamMode: \"values\",\n",
        "  })\n",
        ") {\n",
        "  let msg = messages[messages?.length - 1];\n",
        "  if (msg?.content) {\n",
        "    console.log(msg.content);\n",
        "  } else if (msg?.tool_calls?.length > 0) {\n",
        "    console.log(msg.tool_calls);\n",
        "  } else {\n",
        "    console.log(msg);\n",
        "  }\n",
        "  console.log(\"-----\\n\");\n",
        "}"
      ]
    }
  ],
  "metadata": {
    "jupytext": {
      "encoding": "# -*- coding: utf-8 -*-"
    },
    "kernelspec": {
      "display_name": "TypeScript",
      "language": "typescript",
      "name": "tslab"
    },
    "language_info": {
      "codemirror_mode": {
        "mode": "typescript",
        "name": "javascript",
        "typescript": true
      },
      "file_extension": ".ts",
      "mimetype": "text/typescript",
      "name": "typescript",
      "version": "3.7.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
